/*
 * GrammarReader.java
 *
 * Created on June 24, 2006, 2:05 PM
 *
 */

package galronnlp.io;

import galronnlp.cfg.grammar.*;
import galronnlp.util.Symbol;
import galronnlp.util.SymbolGenerator;

import java.io.FileNotFoundException;
import java.io.File;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.InputStream;
import java.io.IOException;
import java.io.StringReader;
import java.lang.Readable;
import java.nio.channels.ReadableByteChannel;
import java.util.LinkedList;
import java.util.Scanner;
import java.util.regex.*;


/**
 * This class reads in a grammar file of the following format:
 * X --> Y, Z: p
 * X --> Y: p
 * X --> A, B, C, D, E: p
 * 
 * That is, it reads in grammar files with n-ary rules, which may have probabilities
 * associated with each production.
 *
 * @author Daniel A. Galron
 */
public class GrammarReader {
    
    private BufferedReader reader;
    public boolean hasNext = true;
    
    private SymbolGenerator symbolGen;
    
    /** 
     * Creates a new instance of GrammarReader 
     * @param source The file containing the grammar
     * @param gen The symbol generator to generate the Symbols for the terminals and non-terminals
     */
    public GrammarReader(String source, SymbolGenerator gen) {
        symbolGen = gen;
        try {
            reader = new BufferedReader(new FileReader(new File(source)));
        } catch(FileNotFoundException fnfe) {
            System.err.println("Error: File " + source + " not found");
        }
    }

public GrammarReader(String grammarString, SymbolGenerator gen, boolean string) {
        symbolGen = gen;
        reader = new BufferedReader(new StringReader(grammarString));
    }    
    
    /**
     * This method returns a new <code>Rule</code> object to represent the production read.
     * The <code>Rule</code> object can be a <code>UnaryRule</code>, <code>BinaryRule</code>, or
     * <code>NaryRule</code>
     *
     * @return the production rule.
     */
    public Rule readNext() throws malformedGrammarException {
        String ruleString = "";
        try {
            ruleString = reader.readLine();
            // If the end of file has not been released
            if(ruleString != null)  {
                // Tokenize the string by using the arrow as the delimiter
                Scanner scan = new Scanner(ruleString).useDelimiter("\\s*-->\\s*");
                String LHS = "";
                String RHS_Prob = "";
                boolean pcfg = false;
                if(scan.hasNext()) {
                    // Get the LHS (which is the next token for scanning
                    LHS = scan.next();
                    // Read in the right hand side of the rule string, and the probability if
                    // it exists.
                    if(scan.hasNext()) {
                        RHS_Prob = scan.next();
                    } else {
                        // Throw an exception if the rule string does not contain a RHS
                        throw new malformedGrammarException(ruleString);
                    }
                } else {
                    // Throw an exception if the rule string is otherwise not well formed.
                    throw new malformedGrammarException(ruleString);
                }
                String RHS = "";
                double prob = 1.0;
                // Separate the RHS from the probability (if there is one)
                if(RHS_Prob.contains(":")) {
                    pcfg = true;
                    scan = new Scanner(RHS_Prob).useDelimiter("\\s*:\\s*");
                    RHS = scan.next();
                    prob = scan.nextDouble();
                }
                // Otherwise, remove any leading or trailing whitespace
                else {
                    RHS = RHS_Prob.trim();
                }
                LinkedList<Symbol> RHSList = new LinkedList<Symbol>(); 
                // If the rule is a unary rule, create a new Symbol for the Non-Terminal
                // and add it to the list of RHS elements
                if(!RHS.contains(",")) {
                    RHSList.addLast(symbolGen.create(RHS.trim()));
                }
                // Otherwise, use the comma as a delimiter to get the next Symbols
                // in the RHS, and add them to the RHS List
                else {
                    scan = new Scanner(RHS).useDelimiter("\\s*,\\s*");
                    while(scan.hasNext()) {
                        RHSList.addLast(symbolGen.create(scan.next().trim()));
                    }
                }
                Rule rule;
                // Create new pcfg rule if there was a probability found
                if(pcfg) {
                    if(RHSList.size() == 1) {
                        rule = new galronnlp.pcfg.grammar.UnaryRule(symbolGen.create(LHS), RHSList, prob);
                    } else if(RHSList.size() == 2) {
                        rule = new galronnlp.pcfg.grammar.BinaryRule(symbolGen.create(LHS), RHSList, prob);
                    } else {
                        rule = new galronnlp.pcfg.grammar.NaryRule(symbolGen.create(LHS), RHSList, prob);
                    }
                    return rule;
                }
                // Otherwise, create cfg rules
                else {
                    if(RHSList.size() == 1) {
                        rule = new galronnlp.cfg.grammar.UnaryRule(symbolGen.create(LHS), RHSList);
                    } else if(RHSList.size() == 2) {
                        rule = new galronnlp.cfg.grammar.BinaryRule(symbolGen.create(LHS), RHSList);
                    } else {
                        rule = new galronnlp.cfg.grammar.NaryRule(symbolGen.create(LHS), RHSList);
                    }
                    return rule;
                }
            }
            else {
                hasNext = false;
            }
        } catch(Exception e) {
            hasNext = false;
        }
        if(ruleString != null)
            throw new malformedGrammarException(ruleString);
        return null;
    }
    
}
